package risoe.syslab.fhnew.gui;

/*
 * Copyright (c) 2012-2014, Technical University of Denmark (DTU)
 * All rights reserved.
 * 
 * The Flexhouse 2.0 platform ("this software") is licensed under the
 * BSD 3-clause license.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   - Neither the name of DTU nor the names of its contributors may be used to
 *     endorse or promote products derived from this software without specific
 *     prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE TECHNICAL UNIVERSITY OF DENMARK BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

import java.io.File;
import java.net.InetAddress;
import java.rmi.ConnectException;
import java.rmi.RemoteException;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Iterator;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.apache.batik.dom.svg.SAXSVGDocumentFactory;
import org.apache.batik.util.XMLResourceDescriptor;
import org.w3c.dom.svg.SVGDocument;

import risoe.syslab.fhnew.common.RMIUtils;
import risoe.syslab.fhnew.config.PlatformConfig;
import risoe.syslab.fhnew.db.LoggerManager;

/**
 * Class responsible for managing remote graphical user interfaces.
 * @author M. Svendsen
 */
public class UIManager implements UIManagerRemoteInterface{
	// UI Manager interface
	public static final String UI_MANAGER_KEY = "ui_manager_name";
	public static final String UI_MANAGER_DEFAULT_NAME = "UI_Manager";
	private String mUIManagerName;

	private LinkedHashMap<String, VisualizationHolder> mHolderMap = new LinkedHashMap<String, VisualizationHolder>();
	private ArrayList<UIViewerRemoteInterface> mRemoteViewers = new ArrayList<UIViewerRemoteInterface>();
	private LoggerManager mLoggerManager;

	// Heartbeat related services
	private final ScheduledExecutorService mExecutor = Executors.newSingleThreadScheduledExecutor();
	private final Runnable mHeartbeatTask = new Runnable() {
		@Override
		public void run() {
			try {
				// Test all UIViewers
				synchronized (mRemoteViewers) {
					Iterator<UIViewerRemoteInterface> iterator = mRemoteViewers.iterator();
					while(iterator.hasNext()){
						UIViewerRemoteInterface viewer = iterator.next();
						boolean alive = false;
						try {
							alive = viewer.issueHeartBeat();
						} catch (RemoteException e) {}

						if (!alive){
							// Remote UIViewer has terminated - remove it from registry
							System.out.println("Removing Remote UI Viewer: "+viewer.toString());
							iterator.remove();
						}
					}
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	};


	/**
	 * Constructs a new UIManager
	 * @throws RemoteException 
	 */
	public UIManager(LoggerManager logger, PlatformConfig platform) throws RemoteException {
		mUIManagerName = platform.getParameter(UI_MANAGER_KEY, mUIManagerName);
		mLoggerManager = logger;

		// Init RMI
		Registry registry = RMIUtils.getRegistry();
		UIManagerRemoteInterface stub = (UIManagerRemoteInterface)UnicastRemoteObject.exportObject(this, 0);
		registry.rebind(mUIManagerName, stub);

		mLoggerManager.getPlatformEventLogger().logEvent(mUIManagerName,"Init","Bound RMI");

		// Start the heartbeat
		mExecutor.scheduleAtFixedRate(mHeartbeatTask, 0, 1, TimeUnit.SECONDS);
	}

	@Override
	public void addRemoteViewer(UIViewerRemoteInterface viewer) throws RemoteException {
		System.out.println("Adding Remote UI Viewer: "+viewer.toString());
		synchronized (mRemoteViewers) {
			mRemoteViewers.add(viewer);
		}

		// Transfer names of current visualizations to remote viewer
		for (VisualizationHolder holder : mHolderMap.values()){
			try{
				viewer.addVisualization(holder.getName(), holder.getSubscribedObjects());
			} catch (ConnectException ex){
				// Link to remote visualization is dead, remove from viewers
				System.out.println("Removing Remote UI Viewer: "+viewer.toString());
				mRemoteViewers.remove(viewer);
				break;
			} catch (Exception e){
				e.printStackTrace();
			}
		}
	}

	/**
	 * Adds a new visualization to the UI Manager. Calling this function will result in a 
	 * parsing of the SVG file pointed to by the VisualizationHolder.
	 * The UIViewer class must be utilized in order to view the resulting GUI
	 * @param holder a VisualizationHolder describing the visualization to be created
	 */
	public void addVisualization(VisualizationHolder holder){
		// Early exit for controllers without any visualization attached
		if (holder == null) return;

		try {
			// Parse document
			SVGDocument doc = null;
			if (holder.getPath() != null) {
				String parser = XMLResourceDescriptor.getXMLParserClassName();
				SAXSVGDocumentFactory f = new SAXSVGDocumentFactory(parser);
				String uri = new File(holder.getPath()).toURI().toString();
				doc = (SVGDocument) f.createDocument(uri);
			} else {
				doc = (SVGDocument) DocumentUtils.createDocument(holder.getSerializedDocument());
			}
			

			// Save VisualizationHolder
			holder.setDocument(doc);
			mHolderMap.put(holder.getName(), holder);

			// Notify all remote viewers of this update by passing null as the UIViewer argument
			synchronized (mRemoteViewers) {
				Iterator<UIViewerRemoteInterface> iterator = mRemoteViewers.iterator();
				while(iterator.hasNext()){
					UIViewerRemoteInterface viewer = iterator.next();
					try {
						viewer.addVisualization(holder.getName(), holder.getSubscribedObjects());
					} catch (ConnectException ex){
						// Link to remote visualization is dead, remove from viewers
						System.out.println("Removing Remote UI Viewer: "+viewer.toString());
						iterator.remove();
					}
				}
			}

			// Call onDocumentReady Listeners
			holder.notifyOnDocumentPreparedListeners(doc);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Removes the visualization referred to by the String parameter.
	 * @param name the name of the visualization to remove
	 */
	public void removeVisualization(String name){
		// Get visualization
		VisualizationHolder holder = mHolderMap.get(name);
		if (holder == null) return;

		mHolderMap.remove(name);

		// Notify all remote viewers of this update
		synchronized (mRemoteViewers) {
			Iterator<UIViewerRemoteInterface> iterator = mRemoteViewers.iterator();
			while(iterator.hasNext()){
				UIViewerRemoteInterface viewer = iterator.next();
				try {
					viewer.removeVisualization(name);
				} catch (ConnectException ex){
					// Link to remote visualization is dead, remove from viewers
					System.out.println("Removing Remote UI Viewer: "+viewer.toString());
					iterator.remove();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}

	/**
	 * Called by the Controller Manager to signal that a controller has issued a command
	 * to its child visualization.
	 * @param command the VisualizationCommand that was issued by a controller
	 */
	public void executeVisualizationCommand(VisualizationUpdate command){
		// Get the correct VisualizationHolder
		VisualizationHolder holder = mHolderMap.get(command.getDestination());
		if (holder == null || command == null) return;

		// And update the corresponding local document
		DocumentUtils.updateDocument(command, holder.getDocument());

		// Notify all remote viewers of this update
		synchronized (mRemoteViewers) {
			Iterator<UIViewerRemoteInterface> iterator = mRemoteViewers.iterator();
			while(iterator.hasNext()){
				UIViewerRemoteInterface viewer = iterator.next();
				try {
					viewer.updateVisualization(command);
				} catch (Exception ex){
					// Link to remote visualization is dead, remove from viewers
					System.out.println(ex);
					System.out.println("Removing Remote UI Viewer: "+viewer.toString());
					iterator.remove();
				}
			}
		}
	}

	@Override
	public void onVisualizationEvent(VisualizationEvent event)
			throws RemoteException {
		// Notify listeners
		VisualizationHolder holder = mHolderMap.get(event.getDestination());
		if (holder == null) return;

		holder.notifyOnVisualizationEventListeners(event);
	}

	@Override
	public String getSerializedDocument(String visualizationName)
			throws RemoteException {
		// Get the document that corresponds to the visualization in question
		VisualizationHolder holder = mHolderMap.get(visualizationName);

		if (holder != null){
			return DocumentUtils.serializeDocument(holder.getDocument());
		}
		return null;
	}

	@Override
	public boolean issueHeartBeat() throws RemoteException {
		return true;
	}
}

